﻿using System;
using System.Text;
using System.IO;
using System.Collections.Generic;

namespace Steganograf
{
    class LsbHelper
    {
        // Create a Stream which hides the bytes contained 
        // in "message" by using the LSB method
        // (redundancy included: 1 = 01011, 0 = 10100)
        public static byte[] Encode(Stream pInStream, byte[] pMessage,  byte[] pLength, string pPassword)
        {
            int msgIndex = 0, lengthIndex = 0, bytePos = 0;
            int offset = 54;	// Header Size (data starts at byte 54)
            byte[] key = Crypto.GetSha512Key(pPassword); int keyLength = key.Length;
            byte[] myReturn = new byte[pInStream.Length];
            byte[] mySource = new byte[pInStream.Length];
            pInStream.Read(mySource, 0, (int)pInStream.Length);
            pInStream.Seek(0, 0);
            pInStream.Read(myReturn, 0, (int)pInStream.Length);
            
            int index = offset;
            while (lengthIndex < pLength.Length)
            {
                byte byteResult = mySource[index];

                byte bit = Bit.Extract(pLength[lengthIndex], bytePos++); // Extract the bit from the length's byte at proper position
                Bit.Replace(ref byteResult, 0, bit); // Replace the LSB of byteResult with "bit"
                if (bytePos == 8) { bytePos = 0; lengthIndex++; } // Every 8 bits process another byte of "length"

                myReturn[index++] = byteResult;
            }

            if (index + (int)key[index % keyLength] > mySource.Length - 1)
                throw new Exception("Plik źródłowy jest zbyt mały aby pomieścić wpisaną wiadomość");

            index += (int)key[index % keyLength]; //move by the key byte value

            while (msgIndex < pMessage.Length)
            {
                byte byteResult = mySource[index];
                byte bit = Bit.Extract(pMessage[msgIndex], bytePos++);
                foreach (byte redBit in Redundancy.GetRedundantBits(bit))
                {
                    Bit.Replace(ref byteResult, 0, bit);

                    myReturn[index] = byteResult;

                    //TODO: refactoring
                    if (index + (int)key[index % keyLength] > mySource.Length - 1)
                        throw new Exception("Plik źródłowy jest zbyt mały aby pomieścić wpisaną wiadomość");

                    index += (int)key[index % keyLength];

                    byteResult = mySource[index];
                }

                if (bytePos == 8) { bytePos = 0; msgIndex++; }
            }

            return myReturn;
        }

        public static byte[] DecodeLength(byte[] pFile, int pOffset, int pLength)
        {
            byte[] length = new byte[pLength];
            int bytePos = 0, lengthIndex = 0;
            int index = pOffset;
            while (lengthIndex < pLength)
            {
                byte bit = Bit.Extract(pFile[index++], 0); // Extract the LSB of byteRead
                Bit.Replace(ref length[lengthIndex], bytePos++, bit);// Replace the bit at the j position with "bit"
                if (bytePos == 8) { bytePos = 0; lengthIndex++; } // Every 8 bits process another byte of "length"
            }

            return length; // The length var contains the length bytes
        }

        // (redundancy included: 1 = 01011, 0 = 10100)
        public static byte[] Decode(byte[] pFile, int pOffset, int pLength, string pPassword)
        {
            byte[] hidden = new byte[pLength];
            byte[] key = Crypto.GetSha512Key(pPassword); int keyLength = key.Length;
            int bytePos = 0, msgIndex = 0;

            int index = pOffset;

            while (msgIndex < pLength)
            {
                List<byte> readBits = new List<byte>(Redundancy.CODE_LENGTH);
                for (int redIndex = 0; redIndex < Redundancy.CODE_LENGTH; redIndex++)
                {
                    index += (int)key[index % keyLength]; //move by the key byte value
                    readBits.Add(Bit.Extract(pFile[index], 0)); // Extract the LSB of byteRead
                }
                byte bit = Redundancy.GetBitFromRedundancy(readBits.ToArray());
                Bit.Replace(ref hidden[msgIndex], bytePos++, bit);// Replace the bit at the j position with "bit"
                if (bytePos == 8) { bytePos = 0; msgIndex++; } // Every 8 bits process another byte of "hidden"
            }

            return hidden; // The hidden var contains the hidden bytes
        }
    }
}
